// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package staticdata

import (
	"crypto/sha256"
	"fmt"
	"go/constant"
	"internal/buildcfg"
	"io"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"sync"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/objw"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
)

// InitAddrOffset将静态名称符号lsym写入n，它不会修改n。
// 调用者负责确保lsym来自ONAME/PEXTERN节点。
func InitAddrOffset(n *ir.Name, noff int64, lsym *obj.LSym, off int64) {
	if n.Op() != ir.ONAME {
		base.Fatalf("InitAddr n op %v", n.Op())
	}
	if n.Sym() == nil {
		base.Fatalf("InitAddr nil n sym")
	}
	s := n.Linksym()
	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, off)
}

// InitAddr是InitAddrOffset，偏移量固定为0。
func InitAddr(n *ir.Name, noff int64, lsym *obj.LSym) {
	InitAddrOffset(n, noff, lsym, 0)
}

// InitSlice将静态切片符号{lsym，lencap，lencap}写入n+noff，它不会修改n。
// 调用者有责任确保lsym来自ONAME节点。
func InitSlice(n *ir.Name, noff int64, lsym *obj.LSym, lencap int64) {
	s := n.Linksym()
	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, 0)
	s.WriteInt(base.Ctxt, noff+types.SliceLenOffset, types.PtrSize, lencap)
	s.WriteInt(base.Ctxt, noff+types.SliceCapOffset, types.PtrSize, lencap)
}

func InitSliceBytes(nam *ir.Name, off int64, s string) {
	if nam.Op() != ir.ONAME {
		base.Fatalf("InitSliceBytes %v", nam)
	}
	InitSlice(nam, off, slicedata(nam.Pos(), s).Linksym(), int64(len(s)))
}

const (
	stringSymPrefix  = "go.string."
	stringSymPattern = ".gostring.%d.%x"
)

// StringSym返回一个包含字符串s的符号。
// 该符号包含字符串数据，而不是字符串头。
func StringSym(pos src.XPos, s string) (data *obj.LSym) {
	var symname string
	if len(s) > 100 {
		// 为了避免对象文件中的长名称，对大字符串进行哈希处理。
		// 写s的长度也会让人产生一些偏执，
		// 可以防止长度扩展攻击。
		// 下面的fileStringSym知道相同的模式。
		h := sha256.New()
		io.WriteString(h, s)
		symname = fmt.Sprintf(stringSymPattern, len(s), h.Sum(nil))
	} else {
		// 小字符串直接根据其内容命名。
		symname = strconv.Quote(s)
	}

	symdata := base.Ctxt.Lookup(stringSymPrefix + symname)
	if !symdata.OnList() {
		off := dstringdata(symdata, 0, s, pos, "string")
		objw.Global(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL)
		symdata.Set(obj.AttrContentAddressable, true)
	}

	return symdata
}

// maxFileSize是链接器
// （请参阅第9862期）允许的最大文件大小。
const maxFileSize = int64(2e9)

// fileStringSym返回文件内容和大小的符号。
// 如果readonly为true，则符号与任何文字字符串
// 或具有相同内容的其他文件共享存储，并放置在只读节中。
// 如果readonly为false，则该符号是与任何其他符号分开的读写副本，
// 用作[]字节的后备存储。
// 将文件的内容哈希复制到哈希中。（如果hash为nil，则不会复制任何内容。）
// 返回的符号包含数据本身，而不是字符串头。
func fileStringSym(pos src.XPos, file string, readonly bool, hash []byte) (*obj.LSym, int64, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, 0, err
	}
	defer f.Close()
	info, err := f.Stat()
	if err != nil {
		return nil, 0, err
	}
	if !info.Mode().IsRegular() {
		return nil, 0, fmt.Errorf("not a regular file")
	}
	size := info.Size()
	if size <= 1*1024 {
		data, err := ioutil.ReadAll(f)
		if err != nil {
			return nil, 0, err
		}
		if int64(len(data)) != size {
			return nil, 0, fmt.Errorf("file changed between reads")
		}
		var sym *obj.LSym
		if readonly {
			sym = StringSym(pos, string(data))
		} else {
			sym = slicedata(pos, string(data)).Linksym()
		}
		if len(hash) > 0 {
			sum := sha256.Sum256(data)
			copy(hash, sum[:])
		}
		return sym, size, nil
	}
	if size > maxFileSize {
		// ggloblsym需要一个int32，
		// 可能工具链的其他部分
		// 也不能处理这么大的符号。
		// 见戈朗。org/issue/9862。
		return nil, 0, fmt.Errorf("file too large (%d bytes > %d bytes)", size, maxFileSize)
	}

	// 文件太大，无法读取并保存在内存中。
	// 如果只读内容哈希需要或调用方需要，请计算哈希。
	var sum []byte
	if readonly || len(hash) > 0 {
		h := sha256.New()
		n, err := io.Copy(h, f)
		if err != nil {
			return nil, 0, err
		}
		if n != size {
			return nil, 0, fmt.Errorf("file changed between reads")
		}
		sum = h.Sum(nil)
		copy(hash, sum)
	}

	var symdata *obj.LSym
	if readonly {
		symname := fmt.Sprintf(stringSymPattern, size, sum)
		symdata = base.Ctxt.Lookup(stringSymPrefix + symname)
		if !symdata.OnList() {
			info := symdata.NewFileInfo()
			info.Name = file
			info.Size = size
			objw.Global(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL)
			// 注意：此处不能设置AttrContentAddressable，因为内容可寻址处理代码
			// 不知道文件符号。
		}
	} else {
		// 发出一个零长度的数据符号
		// 然后确定使用文件的长度和内容。
		symdata = slicedata(pos, "").Linksym()
		symdata.Size = size
		symdata.Type = objabi.SNOPTRDATA
		info := symdata.NewFileInfo()
		info.Name = file
		info.Size = size
	}

	return symdata, size, nil
}

var slicedataGen int

func slicedata(pos src.XPos, s string) *ir.Name {
	slicedataGen++
	symname := fmt.Sprintf(".gobytes.%d", slicedataGen)
	sym := types.LocalPkg.Lookup(symname)
	symnode := typecheck.NewName(sym)
	sym.Def = symnode

	lsym := symnode.Linksym()
	off := dstringdata(lsym, 0, s, pos, "slice")
	objw.Global(lsym, int32(off), obj.NOPTR|obj.LOCAL)

	return symnode
}

func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int {
	// 太大的对象将立即导致数据段溢出，
	// 导致链接器发出一条隐藏的错误消息。检查此处的超大对象
	// 并提供有用的错误消息。
	if int64(len(t)) > 2e9 {
		base.ErrorfAt(pos, "%v with length %v is too big", what, len(t))
		return 0
	}

	s.WriteString(base.Ctxt, int64(off), len(t), t)
	return off + len(t)
}

var (
	funcsymsmu sync.Mutex // 保护funcsym和相关的包查找（请参见func funcsym）
	funcsyms   []*ir.Name // 需要函数值符号的函数
)

// FuncLinksym返回n.f，n的函数值符号。
func FuncLinksym(n *ir.Name) *obj.LSym {
	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
		base.Fatalf("expected func name: %v", n)
	}
	s := n.Sym()

	// funcsym在这里不仅仅用于保护funcsym的突变（如下），
	// 但同时也是func sym name的包查找，
	// 因为这个函数是从后端并发调用的。
	// 后端没有其他并发包查找，
	// 除了单独保护的类型包。
	// 重复使用FuncSymsum也包括此包查找
	// 避免了一个通用的、更广泛的、昂贵的包查找互斥锁。
	// 注意NeedFuncSym也会对func-sym名称进行包查找，但是它只能从前端串行调用。
	funcsymsmu.Lock()
	sf, existed := s.Pkg.LookupOK(ir.FuncSymName(s))
	// 编译动态链接时不要导出s·f。
	// 动态链接时，必要的函数
	// 符号将使用NeedFuncSym显式创建。
	// 有关详细信息，请参阅NeedFuncSym注释。
	if !base.Ctxt.Flag_dynlink && !existed {
		funcsyms = append(funcsyms, n)
	}
	funcsymsmu.Unlock()

	return sf.Linksym()
}

func GlobalLinksym(n *ir.Name) *obj.LSym {
	if n.Op() != ir.ONAME || n.Class != ir.PEXTERN {
		base.Fatalf("expected global variable: %v", n)
	}
	return n.Linksym()
}

// NeedFuncSym确保fn·f在需要时导出。
// 仅与-dynlink一起使用。
// 当不进行动态链接编译时，
// 根据
// 使用它们的软件包的需要创建Funcsym。
// 通常我们将fn·f存根作为DUPOK符号发出，但是DUPOK不能跨共享库边界工作。
// 因此，当动态链接时，我们只在fn的包中创建
// fn·f存根。
func NeedFuncSym(fn *ir.Func) {
	if base.Ctxt.InParallel {
		// 下面的附加可能只需要锁定
		// funcsymsum，就像在FuncSym中一样。
		base.Fatalf("NeedFuncSym must be called in serial")
	}
	if fn.ABI != obj.ABIInternal && buildcfg.Experiment.RegabiWrappers {
		// 函数值必须始终引用ABI内部
		// 入口点，因此为其他ABI创建
		// funcsym是没有意义的。
		// 
		// /（如果我们不使用ABI包装器，那没关系。）
		base.Fatalf("expected ABIInternal: %v has %v", fn.Nname, fn.ABI)
	}
	if ir.IsBlank(fn.Nname) {
		// 空函数不是唯一的，所以我们不能为它们创建一个
		// funcsym。
		base.Fatalf("NeedFuncSym called for _")
	}
	if !base.Ctxt.Flag_dynlink {
		return
	}
	s := fn.Nname.Sym()
	if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") ||
		(base.Ctxt.Pkgpath == "internal/abi" && (s.Name == "FuncPCABI0" || s.Name == "FuncPCABIInternal")) {
		// 运行时。getg（）、getclosureptr（）、GetCallerRPC（）、getcallersp（）、
		// 和internal/abi。FuncPCABIxxx（）不是实函数，因此
		// 不获取funcsyms。
		return
	}
	funcsyms = append(funcsyms, fn.Nname)
}

func WriteFuncSyms() {
	sort.Slice(funcsyms, func(i, j int) bool {
		return funcsyms[i].Linksym().Name < funcsyms[j].Linksym().Name
	})
	for _, nam := range funcsyms {
		s := nam.Sym()
		sf := s.Pkg.Lookup(ir.FuncSymName(s)).Linksym()
		// 函数值必须始终引用ABI内部
		// 入口点。
		target := s.Linksym()
		if target.ABI() != obj.ABIInternal {
			base.Fatalf("expected ABIInternal: %v has %v", target, target.ABI())
		}
		objw.SymPtr(sf, 0, target, 0)
		objw.Global(sf, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
	}
}

// InitConst将静态文本c写入n。
// n和c都不被修改。
func InitConst(n *ir.Name, noff int64, c ir.Node, wid int) {
	if n.Op() != ir.ONAME {
		base.Fatalf("InitConst n op %v", n.Op())
	}
	if n.Sym() == nil {
		base.Fatalf("InitConst nil n sym")
	}
	if c.Op() == ir.ONIL {
		return
	}
	if c.Op() != ir.OLITERAL {
		base.Fatalf("InitConst c op %v", c.Op())
	}
	s := n.Linksym()
	switch u := c.Val(); u.Kind() {
	case constant.Bool:
		i := int64(obj.Bool2int(constant.BoolVal(u)))
		s.WriteInt(base.Ctxt, noff, wid, i)

	case constant.Int:
		s.WriteInt(base.Ctxt, noff, wid, ir.IntVal(c.Type(), u))

	case constant.Float:
		f, _ := constant.Float64Val(u)
		switch c.Type().Kind() {
		case types.TFLOAT32:
			s.WriteFloat32(base.Ctxt, noff, float32(f))
		case types.TFLOAT64:
			s.WriteFloat64(base.Ctxt, noff, f)
		}

	case constant.Complex:
		re, _ := constant.Float64Val(constant.Real(u))
		im, _ := constant.Float64Val(constant.Imag(u))
		switch c.Type().Kind() {
		case types.TCOMPLEX64:
			s.WriteFloat32(base.Ctxt, noff, float32(re))
			s.WriteFloat32(base.Ctxt, noff+4, float32(im))
		case types.TCOMPLEX128:
			s.WriteFloat64(base.Ctxt, noff, re)
			s.WriteFloat64(base.Ctxt, noff+8, im)
		}

	case constant.String:
		i := constant.StringVal(u)
		symdata := StringSym(n.Pos(), i)
		s.WriteAddr(base.Ctxt, noff, types.PtrSize, symdata, 0)
		s.WriteInt(base.Ctxt, noff+int64(types.PtrSize), types.PtrSize, int64(len(i)))

	default:
		base.Fatalf("InitConst unhandled OLITERAL %v", c)
	}
}
